﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Spore.Utils
{
    public class BinaryFormater
    {
        private static List<Type> CanFormateTypeCache;
        private static List<Type> CannotFormateTypeCache;
        private static Type canFormate = typeof(CanFormatAttribute);
        private static Type formateAttrType = typeof(FormateToBinaryAttribute);

        static BinaryFormater()
        {
            CanFormateTypeCache = new List<Type>();
            CannotFormateTypeCache = new List<Type>();
        }

        public static byte[] FormaterObjectToBinary(object obj)
        {
            Type type = obj.GetType();
            if (type.IsArray)
            {
                return FormaterArrayObject(obj);
            }
            if (type.IsGenericType)
            {
                if (type.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
                {
                    if (obj == null)
                        return BitConverter.GetBytes((int)-1);
                    //Type innerType = type.DeclaringType;
                    object temp = type.GetProperty("Value").GetValue(obj, null);
                    //if()
                    byte[] tempBytes= FormaterObjectToBinary(temp);
                    byte[] ret = new byte[tempBytes.Length+4];
                    Array.Copy(BitConverter.GetBytes(tempBytes.Length), 0, ret, 0, 4);
                    Array.Copy(tempBytes, 0, ret, 4, ret.Length);
                    return ret;
                }
            }
            if (FormaterForBaseType.IsBaseType(type))
            {
                return FormaterForBaseType.FormateBaseType(obj);
            }
            if (!CheckTypeCanBeFormat(type))
                throw new ArgumentException("该类型没有标记CanFormatAttribute,不能格式化！");
            IList<FormateInfo> formatesObjs =new List<FormateInfo>();
            System.Reflection.FieldInfo[] fieldInfos = type.GetFields( BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
            foreach (var fieldInfo in fieldInfos)
            {
                object[] objes = fieldInfo.GetCustomAttributes(formateAttrType, true);
                if (objes.Length < 1) continue;
                var formateAttr = (FormateToBinaryAttribute)objes[0];
                FormateInfo fif = new FormateInfo();
                fif.fielInfo = fieldInfo;
                fif.value = fieldInfo.GetValue(obj);
                formatesObjs.Add(fif);
            }
            PropertyInfo[] propertyInfos = type.GetProperties(BindingFlags.GetProperty|BindingFlags.SetProperty|BindingFlags.Instance|BindingFlags.NonPublic|BindingFlags.Public);
            foreach (var propertyInfo in propertyInfos)
            {
                object[] objes = propertyInfo.GetCustomAttributes(formateAttrType, true);
                if (objes.Length < 1) continue;
                var formateAttr = (FormateToBinaryAttribute)objes[0];
                FormateInfo fif = new FormateInfo();
                fif.propertyInfo = propertyInfo;
                fif.value = propertyInfo.GetValue(obj, null);
                //formatesObjs.Add(propertyInfo.GetValue(obj, null));
            }
            return InnerFormate(formatesObjs);
        }

        private static byte[] InnerFormate(IList<FormateInfo> formatesObjs)
        {
            IList<FormateInfo> fif = formatesObjs;
            List<byte> retBytes = new List<byte>();
            foreach (var v in fif)
            {
                if (v.value == null)
                {
                    retBytes.AddRange(BitConverter.GetBytes((int)-1));
                    continue;
                }
                Type type = v.fielInfo == null ? v.propertyInfo.GetGetMethod().ReturnType : v.fielInfo.FieldType;
                if (FormaterForBaseType.IsBaseType(type))
                {
                    retBytes.AddRange(FormaterForBaseType.FormateBaseType(v.value));
                }
                else
                {
                    if (type.IsGenericType)
                    {
                        if (type.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
                        {
                            if (v.value == null)
                                return BitConverter.GetBytes((int)-1);
                            object temp = type.GetProperty("Value").GetValue(v.value, null);
                            byte[] tempBytes = FormaterObjectToBinary(temp);
                            byte[] ret = new byte[tempBytes.Length + 4];
                            Array.Copy(BitConverter.GetBytes(tempBytes.Length), 0, ret, 0, 4);
                            Array.Copy(tempBytes, 0, ret, 4, tempBytes.Length);
                            retBytes.AddRange(ret);
                            continue;
                        }
                    }
                    byte[] tmp = FormaterObjectToBinary(v.value);
                    retBytes.AddRange(tmp);
                }
            }
            retBytes.InsertRange(0, BitConverter.GetBytes(retBytes.Count));
            return retBytes.ToArray();
        }

        public static byte[] FormaterArrayObject(object obj)
        {
            Array ay = (Array)obj;
            if (ay == null)
                return BitConverter.GetBytes((-1));
            if (ay.Length == 0)
                return new byte[4];
            List<byte> ret = new List<byte>();
            byte[] length = BitConverter.GetBytes(ay.Length);
            ret.AddRange(length);
            for (int i = 0; i < ay.Length; i++)
            {
                ret.AddRange(FormaterObjectToBinary(ay.GetValue(i)));
            }
            return ret.ToArray();
        }

        private static bool CheckTypeCanBeFormat(Type type)
        {
            if (type.IsAbstract | type.IsInterface)
            {
                throw new ArgumentException("抽象类型或接口不能格式化");
            }
            if (CanFormateTypeCache.Contains(type))
                return true;
            if (CannotFormateTypeCache.Contains(type))
                return false;
            object[] objs = type.GetCustomAttributes(canFormate, true);
            if (objs.Length < 1)
            {
                CannotFormateTypeCache.Add(type);
                return false;
            }
            else
            {
                CanFormatAttribute cfa = (CanFormatAttribute)objs[0];
                if (!cfa.CanFormat)
                {
                    CannotFormateTypeCache.Add(type);
                    return false;
                }
                else
                {
                    CanFormateTypeCache.Add(type);
                    return true;
                }
            }
        }

        public static object DeformateObject(Type type,byte[] data, int startIndex,out int length)
        {
            length = 4;
            if (type.IsArray)
            {
               return DeformateArrayObj(type.GetElementType(), data, startIndex, out length);
            }
            else if (type.IsGenericType)
            {
                if (type.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
                {
                    int tmp_length = BitConverter.ToInt32(data,startIndex);
                    if (tmp_length == -1)
                    {
                        length = 4;
                        return null;
                    }
                    length = tmp_length + 4;
                    Type innerType = type.GetProperty("Value").GetGetMethod().ReturnType;
                    int flag=0;
                    object temp = DeformateObject(innerType, data, startIndex, out flag);
                    return temp;
                }
            }
             if (FormaterForBaseType.IsBaseType(type))
            {
                return FormaterForBaseType.DeFormateBaseObj(type, data, startIndex, out length);
            }
            
            else if (!CheckTypeCanBeFormat(type))
            {
                throw new ArgumentException("该类型不能被反序列化");
            }
            else
            {
                int objLength = BitConverter.ToInt32(data, startIndex);
                if (objLength == -1)
                    return null;
                length = objLength + 4;
                List<FormateInfo> fif= new List<FormateInfo>();
                object obj = Activator.CreateInstance(type);
                FieldInfo[] fieldInfos = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                for (int i = 0; i < fieldInfos.Length; i++)
                {
                  object[] attrs=  fieldInfos[i].GetCustomAttributes(formateAttrType, true);
                  if (attrs.Length == 0) continue;
                  FormateToBinaryAttribute formateAttr = (FormateToBinaryAttribute)attrs[0];
                  Type fieldType = fieldInfos[i].FieldType;
                  FormateInfo fi = new FormateInfo();
                  fi.fielInfo = fieldInfos[i];
                  fi.type = fieldType;
                  fif.Add(fi);
                }
                PropertyInfo[] propertyInfos = type.GetProperties(BindingFlags.GetProperty | BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                for (int i = 0; i < propertyInfos.Length; i++)
                {
                    object[] attrs = propertyInfos[i].GetCustomAttributes(formateAttrType, true);
                    if (attrs.Length == 0) continue;
                    FormateToBinaryAttribute formateAttr = (FormateToBinaryAttribute)attrs[0];
                    Type fieldType = propertyInfos[i].GetSetMethod().GetParameters()[0].ParameterType;
                    FormateInfo fi = new FormateInfo();
                    fi.propertyInfo = propertyInfos[i];
                    fi.type = fieldType;
                    fif.Add(fi);
                }
                int progress =4;
                for (int i = 0; i < fif.Count; i++)
                {
                    FormateInfo fi = fif[i];
                    int temp_flag = 0;
                    object tempObj = DeformateObject(fi.type, data, progress+startIndex, out temp_flag);
                    if (fi.fielInfo != null)
                        fi.fielInfo.SetValue(obj, tempObj);
                    else if (fi.propertyInfo != null)
                    {
                        fi.propertyInfo.SetValue(obj, tempObj, null);
                    }
                    progress += temp_flag;
                }
                return obj;
            }
        }

        public static object DeformateArrayObj(Type eleType,byte[] data, int startIndex, out int length)
        {
            length = 4;
            int eleCount = BitConverter.ToInt32(data, startIndex);
            if (eleCount == -1)
            {
                return null;
            }
            Array array = Array.CreateInstance(eleType, eleCount);
             int progress=startIndex+4;
            for (int i = 0; i < eleCount; i++)
            {
                int temp = 0;
                object obj=  DeformateObject(eleType, data, progress, out temp);
                array.SetValue(obj, i);
                progress+=temp;
            }
            length = progress - startIndex;
            return array;
        }

        

    }
}
